import numpy as np
import pandas as pd

# 损失函数
def cost_function(X, Y, B):
    m = len(Y)
    J = np.sum((X.dot(B) - Y) ** 2) / (2 * m)
    return J

# 梯度下降 递归逼近
def gradient_descent(X, Y, B, alpha, iterations):
    cost_history = [0] * iterations
    m = len(Y)

    for iteration in range(iterations):
        # Hypothesis Values
        h = X.dot(B)
        # Difference b/w Hypothesis and Actual Y
        loss = h - Y
        # Gradient Calculation
        gradient = X.T.dot(loss) / m
        # Changing Values of B using Gradient
        B = B - alpha * gradient
        # New Cost Value
        cost = cost_function(X, Y, B)
        cost_history[iteration] = cost

    return B, cost_history

# 在numpy数组中找到最接近给定的值
def find_nearest(array, value):
    idx = (np.abs(array-value)).argmin()
    return array[idx]

if __name__ == '__main__':
    csv_file = '../../data/Iris.csv'
    ds = pd.read_csv(csv_file)

    cols = ds.columns.tolist()  # 表头
    ds_col_len = len(ds[cols[0]])
    X_train = ds[cols[:-1]]  # 训练数据 标签
    X_train = np.array(X_train.assign(e=pd.Series(np.ones(ds_col_len)).values).values)  # 加一列1,作为多元线性回归中的常数项参数
    Y_train = ds[cols[-1]].values  # 训练数据 标签对应的分类
    # 分类是字符串，线性回归需要映射成 实数来处理
    cla = list(set(Y_train))
    for i in range(len(Y_train)):
        Y_train[i] = cla.index(Y_train[i]) + 1  # 加不加1都行
    Y_train = np.array(Y_train)
    # print(np.array(Y_train))
    # print(X_train)


    B = np.zeros(len(cols))  # 回归方程的待求参数，初始化
    alpha = 0.0001
    inital_cost = cost_function(X_train, Y_train, B)
    print("Initial Cost")
    print(inital_cost)

    print('正在进行参数估计...')
    # 100000 Iterations
    newB, cost_history = gradient_descent(X_train, Y_train, B, alpha, 100000)

    # New Values of B
    print("\n系数为：")
    print(newB)

    # Final Cost of new B
    print("Final Cost")
    print(cost_history[-1])

    # 预测
    # 6.5, 2.8, 4.6, 1.5
    predict = np.dot(newB, np.array([6.5, 2.8, 4.6, 1.5, 1]).T)
    print('预测举例：\n[6.5, 2.8, 4.6, 1.5]的函数值为：')
    print(predict)
    print('其分类为：')
    nearest = find_nearest(Y_train, predict) - 1
    print(cla[nearest])
